package ar.edu.itba.pod.main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.NotBoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.joda.time.Duration;

import ar.edu.itba.balance.api.AgentsTransfer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.pod.agent.market.Consumer;
import ar.edu.itba.pod.agent.market.Market;
import ar.edu.itba.pod.agent.market.Producer;
import ar.edu.itba.pod.agent.runner.Agent;
import ar.edu.itba.pod.agent.runner.Simulation;
import ar.edu.itba.pod.legajo45129.balance.api.Balancer;
import ar.edu.itba.pod.legajo45129.manager.NodeManager;

import com.google.common.base.Preconditions;

public class NodeUI {

	private static String remoteIp;
	private static int remotePort;
	private static Logger logger = Logger.getLogger(NodeUI.class);
	private static CommandLineParser parser = new GnuParser();
	private static Options options = new Options();
	private static Option connectedNodes;
	private static Option connect;
	private static Option createGroup;
	private static Option disconnect;
	private static Option help;
	private static Option coordinator;

	private static HelpFormatter formatter = new HelpFormatter();
	private static int port = 9999;
	private static String addr;
	private static Option setNodeId;
	private static Option getNodeId;
	private static Option addProducer;
	private static Option addConsumer;
	private static Option addMarket;
	private static Option loggerLevel;
	private static Option ip;
	private static Option portOption;
	private static Option start;
	private static Option stop;
	private static Option stopAndGet;
	private static Option runAgentsOnNode;
	private static Option remote;
	private static Option balance;
	private static Option shutdown;
	
	private static Option agentsOnNode;

	public static void main(String[] args) throws InterruptedException {
		BasicConfigurator.configure();
		NodeUI.initOptions();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		CommandLine cmd;
		
		try {
			parse(args);
			logger.info("node ip: " + addr + " port: " + port);
			
			Simulation simulation = SimulationFactory.createSimulation();
			NodeManager.getInstance().configure(addr, port , Long.toString(System.currentTimeMillis()), simulation);
		} catch (UnknownHostException e1) {
			System.out.println("unknown host " + e1.getMessage());
			System.exit(-1);
		}

		while (true) {
			String line = null;

			try {
				line = br.readLine();
				String[] arg = line.split(" ");

				cmd = parser.parse(options, arg);

				if (cmd.hasOption(createGroup.getOpt())) {
					NodeManager.getInstance().getClusterAdministration().createGroup();
				} else if (cmd.hasOption(connect.getOpt())) {
					String[] values = cmd.getOptionValues(connect.getOpt());
					NodeManager.getInstance().getClusterAdministration().connectToGroup(values[0], Integer.parseInt(values[1]));
				} else if (cmd.hasOption(help.getOpt())) {
					formatter.printHelp("options:", options);
				}  else if (cmd.hasOption(connectedNodes.getOpt())) {
					showConnectedNodes(NodeManager.getInstance().getClusterAdministration().connectedNodes());
				} else if (cmd.hasOption(coordinator.getOpt())) {
					NodeInformation c = NodeManager.getInstance().getCoordinator();
					System.out.println(c == null ? "null" : c);
				} else if (cmd.hasOption(getNodeId.getOpt())) {
					System.out.println(NodeManager.getInstance().getNodeInformation().id());
				} else if (cmd.hasOption(setNodeId.getOpt())) {
					String[] values = cmd.getOptionValues(setNodeId.getOpt());
					NodeInformation orig = NodeManager.getInstance().getNodeInformation();
					NodeManager.getInstance().setNodeInformation(new NodeInformation(orig.host(), orig.port(), values[0]));
				} else if (cmd.hasOption(addConsumer.getOpt())) {
					String values = cmd.getOptionValue(addConsumer.getOpt());
					Consumer c = getConsumer(values);
					if (c != null) {
						NodeManager.getInstance().getSimulation().add(c);
						System.out.println(c + " added");
					}
				} else if (cmd.hasOption(addProducer.getOpt())) {
					String values = cmd.getOptionValue(addProducer.getOpt());
					Producer p = getProducer(values);
					if (p != null) {
						NodeManager.getInstance().getSimulation().add(p);
						System.out.println(p + " added");
					}
				} else if (cmd.hasOption(addMarket.getOpt())) {
					String values = cmd.getOptionValue(addMarket.getOpt());
					Market c = getMarket(values);
					if (c != null) {
						NodeManager.getInstance().getSimulation().add(c);
						System.out.println(c + " added");
					}
				}else if (cmd.hasOption(stop.getOpt())) {
					Simulation simulation = NodeManager.getInstance().getSimulation();
					Duration remaining = simulation.remaining();
					try {
						
						simulation.stop();
					} catch (IllegalStateException e) {
						System.out.println(e.getLocalizedMessage());
					}
					System.out.println("stop simulation, remaining time is: " + remaining.getMillis());
				} else if (cmd.hasOption(start.getOpt())) {
					long t = Long.parseLong(cmd.getOptionValue(start.getOpt()));
					NodeManager.getInstance().getSimulation().start(Duration.millis(t));
				} else if (cmd.hasOption(stopAndGet.getOpt())) {
					String value = cmd.getOptionValue(stopAndGet.getOpt());
					AgentsTransfer at;
					if (remoteIp != null) {
						at = NodeManager.getInstance().getAgentsTransfer(remoteIp, remotePort);
					} else {
						at = NodeManager.getInstance().getAgentsTransfer();
					}
					if (value != null) {
						List<NodeAgent> nodeAgents = at.stopAndGet(Integer.parseInt(value));
						printNodeAgentsList(nodeAgents);
					}
					
				} else if (cmd.hasOption(runAgentsOnNode.getOpt())) {
					String[] value = cmd.getOptionValues(runAgentsOnNode.getOpt());
					AgentsTransfer at;
					if (remoteIp != null) {
						at = NodeManager.getInstance().getAgentsTransfer(remoteIp, remotePort);
					} else {
						at = NodeManager.getInstance().getAgentsTransfer();
					}
					
					if (value != null && value.length == 3) {
						List<NodeAgent> nodeAgents = getNodeAgentsList(value);
						printNodeAgentsList(nodeAgents);
						at.runAgentsOnNode(nodeAgents);
					} else {
						System.out.println("wrong command: " + runAgentsOnNode.getDescription());
					}
				} else if (cmd.hasOption(remote.getOpt())) {
					String[] values = cmd.getOptionValues(remote.getOpt());
					remoteIp = "null".equals(values[0]) ? null : values[0];
					remotePort = Integer.parseInt(values[1]);
				} else if (cmd.hasOption(balance.getOpt())) {
					Balancer.balance(NodeManager.getInstance().getCoordinator());
				} else if (cmd.hasOption(agentsOnNode.getOpt())) {
					List<Agent> agentsRunning = NodeManager.getInstance().getSimulation().getAgentsRunning();
					for (Agent a: agentsRunning) {
						System.out.println(a);
					}
				} else if (cmd.hasOption(shutdown.getOpt())) {
					NodeManager.getInstance().shutdown();
				} else if (cmd.hasOption(disconnect.getOpt())) {
					NodeManager.getInstance().getClusterAdministration().disconnectFromGroup(NodeManager.getInstance().getNodeInformation());
				}

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ParseException e) {
				System.out.println("wrong command");
			} catch (NumberFormatException e) {
				System.out.println("Invalid number");
			} catch (NotBoundException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			}
		}
	}

	private static List<NodeAgent> getNodeAgentsList(String[] values) {
		List<NodeAgent> ret = new ArrayList<NodeAgent>();
		String agentType = values[0];
		String resource = values[1];
		int amount = Integer.parseInt(values[2]);
		if ("p".equals(agentType)) {
			for (int i = 0; i < amount; i++) {
				ret.add(new NodeAgent(NodeManager.getInstance().getNodeInformation(), getProducer(resource)));
			}
		} else if ("c".equals(agentType)) {
			for (int i = 0; i < amount; i++) {
				ret.add(new NodeAgent(NodeManager.getInstance().getNodeInformation(), getConsumer(resource)));
			}
		} else if ("m".equals(agentType)) {
			for (int i = 0; i < amount; i++) {
				ret.add(new NodeAgent(NodeManager.getInstance().getNodeInformation(), getMarket(resource)));
			}
		}
		
		if (ret.size() > 0) {
			return ret;
		}
		
		throw new IllegalArgumentException("unknown agent type");
	}

	private static void printNodeAgentsList(List<NodeAgent> nodeAgents) {
		for (NodeAgent n: nodeAgents) {
			System.out.println(n.node() + " | " + n.agent());
		}
	}


	private static void parse(String[] args) throws UnknownHostException {
		try {
			Level level = Level.INFO;
			CommandLine cmd = parser.parse(options, args);
			if (cmd.hasOption(loggerLevel.getOpt())) {
				String[] values = cmd.getOptionValues(loggerLevel.getOpt());
				if ("DEBUG".equals(values[0])) {
					level = Level.DEBUG;
				} else if ("TRACE".equals(values[0])) {
					level = Level.TRACE;
				} 
			} 
			
			if (cmd.hasOption(ip.getOpt())) {
				addr = cmd.getOptionValue(ip.getOpt());
			} else {
				addr = InetAddress.getLocalHost().getHostAddress();
			}
			
			if (cmd.hasOption(portOption.getOpt())) {
				port = Integer.parseInt(cmd.getOptionValue(portOption.getOpt()));
			} 
			
			Logger.getRootLogger().setLevel(level);
			System.out.println("Logger level: " + level.toString());
		} catch (ParseException e) {
			Logger.getRootLogger().setLevel(Level.INFO);
			System.out.println("Parse logger level error, set level to INFO");
		}
		
	}

	private static Market getMarket(String values) {
		if ("steel".equals(values)) {
			return new Market("steel market", SimulationFactory.getSteel());
		} else if ("copper".equals(values)) {
			return new Market("copper market", SimulationFactory.getCopper());
		} else if ("gold".equals(values)) {
			return new Market("gold market", SimulationFactory.getGold());
		}
		throw new IllegalArgumentException("unknown market type");
	}
	
	private static Producer getProducer(String values) {
		if ("steel".equals(values)) {
			return new Producer("steel mine", SimulationFactory.getSteel(), Duration.standardDays(1), 5);
		} else if ("copper".equals(values)) {
			return new Producer("copper mine", SimulationFactory.getCopper(), Duration.standardDays(1), 10);
		} else if ("gold".equals(values)) {
			return new Producer("gold mine", SimulationFactory.getGold(), Duration.standardDays(1), 1);
		}
		throw new IllegalArgumentException("unknown producer type");
	}

	private static Consumer getConsumer(String values) {
		if ("steel".equals(values)) {
			return new Consumer("steel consumer", SimulationFactory.getSteel(), Duration.standardDays(1), 2);
		} else if ("copper".equals(values)) {
			return new Consumer("copper consumer", SimulationFactory.getCopper(), Duration.standardHours(1), 2);
		} else if ("gold".equals(values)) {
			return new Consumer("gold consumer", SimulationFactory.getGold(), Duration.standardHours(1), 4);
		}
		throw new IllegalArgumentException("unknown consumer type");
	}

	private static void showConnectedNodes(Set<NodeInformation> nodes) {
		Preconditions.checkNotNull(nodes);
		for (NodeInformation n: nodes) {
			System.out.println("-> " + n);
		}
	}

	private static void initOptions() {
		ip = new Option("ip", "ip");
		ip.setArgs(1);
		options.addOption(ip);
		
		portOption = new Option("port", "port");
		portOption.setArgs(1);
		options.addOption(portOption);
		
		loggerLevel = new Option("l", "set logger level");
		loggerLevel.setArgs(1);
		options.addOption(loggerLevel);
		
		connectedNodes = new Option("connectedNodes", "show my connected nodes");
		options.addOption(connectedNodes);

		connect = new Option("connect", "connect to group with given node id (URI) as entry point");
		connect.setArgs(2);
		options.addOption(connect);

		createGroup = new Option("createGroup", "create a group for this node");
		options.addOption(createGroup);

		disconnect = new Option("disconnect", "disconnect from the group");
//		disconnect.setArgs(1);
		options.addOption(disconnect);

		help = new Option("help", "print options");
		options.addOption(help);

		coordinator = new Option("coordinator", "show the coordinator of this node");
		options.addOption(coordinator);

		getNodeId = new Option("getNodeId", "get node id");
		options.addOption(getNodeId);

		setNodeId = new Option("setNodeId", "set node id");
		setNodeId.setArgs(1);
		options.addOption(setNodeId);
		
		addProducer = new Option("addP", "add producer");
		addProducer.setArgs(1);
		options.addOption(addProducer);
		
		addConsumer = new Option("addC", "add consumer");
		addConsumer.setArgs(1);
		options.addOption(addConsumer);
		
		addMarket = new Option("addM", "add market");
		addMarket.setArgs(1);
		options.addOption(addMarket);
		
		start = new Option("start", "start simulation");
		start.setArgs(1);
		options.addOption(start);
		
		stop = new Option("stop", "stop simulation");
		options.addOption(stop);
		
		stopAndGet = new Option("sag", "stopAndGet");
		stopAndGet.setArgs(1);
		options.addOption(stopAndGet);
		
		runAgentsOnNode = new Option("run", "run agents on node, <p | c | m> <steel | gold | copper> <num>");
		runAgentsOnNode.setArgs(3);
		options.addOption(runAgentsOnNode);
		
		remote = new Option("remote", "set remote node to execute his methods");
		remote.setArgs(2);
		options.addOption(remote);
		
		balance = new Option("balance", "balance agents on every node through coordinator");
		options.addOption(balance);
		
		agentsOnNode = new Option("a", "show running agents on this node");
		options.addOption(agentsOnNode);
		
		shutdown = new Option("down", "shutdown this node");
		options.addOption(shutdown);
	}

}
